

import cn.hutool.json.JSONUtil;
import entity.ContainerStats;
import entity.MasterConfig;
import entity.ServerDockerContainterProcess;

import com.fasterxml.jackson.databind.ObjectMapper;
import mod.pojo.DSTMod;
import mod.service.DSTModService;
import service.DSTConsoleService;
import service.DSTServerDockerService;
import utils.Base;
import utils.DockerManager;
import utils.ServerState;
import utils.ShellUtil;
import view.DSTServerProcessView;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.io.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.*;

public class MultiRouteServlet extends HttpServlet {
    private static Instant startTime = Instant.now();
    private LinkedList<ServerDockerContainterProcess> serverProcessesList;
    private ServerState serverState = new ServerState();
    private DSTServerDockerService dstServerDockerService;
    private MasterConfig properties;
    private DSTConsoleService dstConsoleService = new DSTConsoleService();

    MultiRouteServlet(LinkedList<ServerDockerContainterProcess> serverList,
        DSTServerDockerService dstServerDockerService,
        MasterConfig config
    ) {
        this.serverProcessesList = serverList;
        this.dstServerDockerService = dstServerDockerService;
        this.properties = config;
    }


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取请求路径
        String path = req.getPathInfo();

        // 设置响应内容类型


        // 根据路径返回不同的响应
        if ("/hello".equals(path)) {
            PrintWriter out = resp.getWriter();
            out.println("<h1>Hello from MultiRouteServlet!</h1>");
        } else if ("/system_state".equals(path)) {
            getSystemState(req, resp);
        } else if ("/services".equals(path)) {
            getServerStateList(req, resp);
        } else if ("/callBackServerByArchiveID".equals(path)) {
            callBackServerByArchiveID(req, resp);
        } else if ("/shutdownAllServer".equals(path)) {
            shutdownAllServer(req, resp);
        } else if ("/getModsInfo".equals(path)) {
            getModsInfo(req, resp);
        } else if ("/ackCallBackServer".equals(path)) {
            ackCallBackServer(req, resp);
        } else if (path.startsWith("/console")) {
            console(req, resp);
        } else if ("/updateDst".equals(path)) {
            updateDst(req, resp);
        } else if ("/test".equals(path)) {
            test(req, resp);
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取请求路径
        String path = req.getPathInfo();

        // 设置响应内容类型
        resp.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html");


        // 根据路径处理不同的 POST 请求
        if ("/post_test".equals(path)) {
            PrintWriter out = resp.getWriter();
            String name = req.getParameter("name");
            out.println("<h1>Form Submitted! Hello, " + (name != null ? name : "Guest") + "!</h1>");
        } else if ("/buildNewServer".equals(path)) {
            buildNewServer(req, resp);
        } else if ("/startServerByArchiveID".equals(path)) {
            startServerByArchiveID(req, resp);
        } else if ("/shutdownServer".equals(path)) {
            shutdownServer(req, resp);
        }
    }


    /**
     * 获取系统状态、cpu占用、内存状态
     *
     * @param req
     * @param resp
     * @throws IOException
     */
    private void getSystemState(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        System.out.println("request /system_state");

        resp.setStatus(HttpServletResponse.SC_OK);
        resp.setContentType("application/json");

        PrintWriter writer = resp.getWriter();
        ObjectMapper objectMapper = new ObjectMapper();

        try {
            String json = objectMapper.writeValueAsString(serverState.watchSystem());
            writer.println(json);
        } catch (Exception e) {
            resp.setStatus(801);
            writer.println("Error map json");
            e.printStackTrace();
        }
    }

    /**
     * 获取所有游戏服务器的状态信息
     *
     * @param req
     * @param resp
     * @throws IOException
     */
    private void getServerStateList(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        System.out.println("request /services");

        resp.setStatus(HttpServletResponse.SC_OK);
        resp.setContentType("application/json");

        PrintWriter writer = resp.getWriter();
        ObjectMapper objectMapper = new ObjectMapper();

        List<DSTServerProcessView> views = new ArrayList<>();
        for (ServerDockerContainterProcess dstServerProcess : serverProcessesList) {
            if (dstServerProcess.getState() == 1) {
                // 玩家数
                int playerNum = -1;
                Path logPath = Paths.get(properties.getArchivePath(), 
                    dstServerProcess.getArchiveID(), dstServerProcess.getTag(), "server_log.txt");

                ArrayList<String> playerList = new ArrayList<>();
                if (dstConsoleService.getPlayerList(logPath, playerList) == 0)
                {
                    playerNum = playerList.size();
                    System.out.println("player list: " + playerList);
                    System.out.println("player list size: " + playerList.size());

                    dstServerProcess.setPlayerNum(playerNum);

                    if (playerNum == 0) {
                        dstServerProcess.setLastHaveNoPlayerTime(LocalDateTime.now());
                    }

                } else {
                    System.err.println("get player list err.");
                }

                // 是否初始化
                if (!dstServerProcess.isInited()) {
                    if (Base.haveString(logPath.toAbsolutePath().toString(), 
                    "MasterPort:")) {
                        dstServerProcess.setInited(true);
                    };
                }

                ContainerStats stats = dstServerDockerService.getStats(dstServerProcess.getContainterID());
                DSTServerProcessView view = new DSTServerProcessView(
                        dstServerProcess.getState(),
                        dstServerProcess.isInited() ? 1 : 0,
                        dstServerProcess.getArchiveID(),
                        dstServerProcess.getPlayerNum(),
                        stats.getMemoryUsage(),
                        (double)stats.getMemoryUsage() / (double)stats.getMemoryLimit(),
                        stats.getCpuTotalUsage(),
                        dstServerProcess.getTag(), 
                        String.valueOf(dstServerProcess.getCpu_limit()),
                        dstServerProcess.getMemery_limit()+"MB",
                        dstServerProcess.getSwap_limit()+"MB"
                );

                // 计算两个时间点之间的持续时间
                Duration duration = Duration.between(
                    dstServerProcess.getLastHaveNoPlayerTime(), LocalDateTime.now());

                // 获取持续时间中的分钟数
                long minutes = duration.toMinutes();

                view.setMinutesNoPlayer(minutes);
                views.add(view);
            } else {
                DSTServerProcessView view = new DSTServerProcessView(
                        0,
                        0,
                        dstServerProcess.getArchiveID(),
                        -1,
                        -1L,
                        -1.0,
                        -1L,
                        dstServerProcess.getTag(),
                        String.valueOf(dstServerProcess.getCpu_limit()),
                        dstServerProcess.getMemery_limit()+"MB",
                        dstServerProcess.getSwap_limit()+"MB"
                );
                views.add(view);
            }

        }

        try {
            String json = objectMapper.writeValueAsString(views);
            writer.println(json);
        } catch (Exception e) {
            resp.setStatus(801);
            writer.println("Error map json");
            e.printStackTrace();
        }
    }

    /**
     * 创建新的游戏服务
     *
     * @param req
     * @param resp
     * @throws IOException
     */
    private void buildNewServer(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        System.out.println("request /buildNewServer");

        resp.setStatus(801);

        PrintWriter out = resp.getWriter();

        String p_shard = req.getParameter("shard");
        String archiveID = req.getParameter("archiveID");
        Integer memery;
        Float cpupercent;
        String[] shards;
        try {
            shards = p_shard.split(",");
            memery = Integer.parseInt(req.getParameter("memery_limit"));
            cpupercent = Float.parseFloat(req.getParameter("cpu_limit"));
        } catch(Exception e)
        {
            e.printStackTrace();
            System.err.println("参数错误");
            out.println("参数错误");
            return ;
        }
        if(p_shard == null || shards == null || shards.length==0 || archiveID == null)
        {
            System.err.println("参数错误");
            out.println("参数错误");
            return ;
        }

        for (ServerDockerContainterProcess scp: serverProcessesList) {
            if (scp.getArchiveID().equals(archiveID)) {
                System.err.println("archive已存在");
                out.println("archive已存在, archiveID: " + archiveID);
                return ;
            }
        }



        if (ServletFileUpload.isMultipartContent(req)) {
            try {
                // 创建文件项工厂
                DiskFileItemFactory factory = new DiskFileItemFactory();
                // 创建文件上传处理器
                ServletFileUpload upload = new ServletFileUpload(factory);

                // 解析请求
                List<FileItem> items = upload.parseRequest(req);

                if(items.size() == 0)
                {
                    String str = "上传文件为空";
                    resp.setStatus(805);
                    System.err.println(str);
                    out.println(str);
                    return ;
                }

                FileItem item = items.get(0);
                String itemFileName = item.getName();
                for (ServerDockerContainterProcess dstServerProcess: serverProcessesList)
                {
                    if (dstServerProcess.getArchiveID().equals(itemFileName))
                    {
                        String str = "存档" + itemFileName + "已存在";
                        resp.setStatus(806);
                        System.err.println(str);
                        out.println(str);
                        return ;
                    }
                }

                
                if (!item.isFormField()) {
                    // 处理上传的文件
                    String fileName = new File(item.getName()).getName();
                    String filePath = properties.getUploadDir() + File.separator + fileName;
                    File storeFile = new File(filePath);
                    // System.out.println(storeFile.getAbsolutePath());

                    if (storeFile.exists()) storeFile.delete();

                    // 保存文件
                    item.write(storeFile);
                    System.out.println("File uploaded successfully: " + fileName);

                    // todo 文件验证
                    Base.archVolatile(storeFile, "");

                    Path archivePath = Paths.get(properties.getArchivePath()).toAbsolutePath();
                    File archiveFile = new File(archivePath.toString());
                    if (Base.extractTarGz(storeFile, archiveFile) != 0)
                    {
                        resp.setStatus(804);
                        String resstr = storeFile.toString() + " extract fail.";
                        System.err.println(resstr);
                        out.println(resstr);
                        Base.deleteDirectory(archiveFile);
                        return ;
                    }
                    System.out.println("extract successfully: " + fileName);

                    Path dstPath = Paths.get(properties.getDstPath());
                    Path dstBinPath = dstPath.resolve("bin");
                    Path dstExePath = dstBinPath.resolve("dontstarve_dedicated_server_nullrenderer");

                    String img = properties.getDst_docker_image();
                    if(img == null)
                    {
                        System.err.println("config dst_docker_image is null.");
                        Base.deleteDirectory(archiveFile);
                        resp.setStatus(801);
                        return ;
                    }

                    ArrayList<ServerDockerContainterProcess> temps = new ArrayList<>();
                    for (String shard : shards) {
                        ServerDockerContainterProcess dstServerProcess = dstServerDockerService.createNewServerContainerWithLimit(
                            img, archiveID, shard, 
                            memery / 2, memery,
                            cpupercent
                        );
                        if (dstServerProcess == null)
                        {
                            String resstr = "docker container create fail: " + archiveID;
                            System.err.println(resstr);
                            Base.deleteDirectory(archiveFile);
                            resp.setStatus(804);
                            out.println(resstr);
                            return ;
                        }

                        temps.add(dstServerProcess);
                    }

                    for (ServerDockerContainterProcess dstServerProcess : temps) {
                        this.serverProcessesList.add(dstServerProcess);
                    }

                    storeFile.delete();

                    out.println("Build successfully: " + archiveID);
                    resp.setStatus(HttpServletResponse.SC_OK);
                }
            } catch (FileUploadException e) {
                out.println("File upload failed.");
                System.out.println(e.getMessage());
                e.printStackTrace();
            } catch (Exception e) {
                out.println("File upload failed.");
                System.out.println("Error: " + e.getMessage());
                e.printStackTrace();
            }
        } else {
            out.println("Request does not contain upload data");
        }
    }

    /**
     * 通过存档ID开启服务
     *
     * @param req
     * @param resp
     * @throws IOException
     */
    private void startServerByArchiveID(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        System.out.println("request /startServerByArchiveID");
        // 设置响应类型
        resp.setContentType("text/plain");
        PrintWriter out = resp.getWriter();
        String archiveID = req.getParameter("archiveID");
        String shard = req.getParameter("shard");
        for (ServerDockerContainterProcess dstServerProcess : serverProcessesList) {
            if (dstServerProcess.getArchiveID().equals(archiveID) && dstServerProcess.getTag().equals(shard)) {

                Path archivePath = Paths.get(properties.getArchivePath()).toAbsolutePath();
                // 安装mod
                DSTModService dstModService = new DSTModService(properties.getDstPath());
                File modfile = archivePath.resolve(archiveID).resolve(shard).resolve("modoverrides.lua").toFile();
                if (dstModService.installModToServer(modfile.toPath())==0)
                {
                    System.out.println("install mod sucess.");
                }
                
                dstServerDockerService.startContainer(dstServerProcess.getContainterID());
                out.println("success");
                resp.setStatus(200);
                return ;
            }
        }

        out.println("no server start");
        resp.setStatus(801);
    }

    /**
     * 根据archiveID关闭游戏服务器
     *
     * @param req
     * @param resp
     * @throws IOException
     */
    private void shutdownServer(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        System.out.println("request /shutdownServer");
        // 设置响应类型
        resp.setContentType("text/plain");
        String archiveID = req.getParameter("archiveID");
        PrintWriter out = resp.getWriter();

        out.println("success");
        resp.setStatus(200);

        boolean flag = false;
        for (ServerDockerContainterProcess sp : this.serverProcessesList) {
            if (sp.getArchiveID().equals(archiveID) && sp.getState() == 1) {
                System.out.println("start shutdown " + archiveID);

                dstConsoleService.shutdownServer();

                try {
                    Thread.sleep(3 * 1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }

                dstServerDockerService.stopContainer(sp.getContainterID());
                flag = true;
            }
        }

        if(flag) return;

        out.println("fail");
        resp.setStatus(801);
    }

    private void shutdownAllServer(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        System.out.println("request /shutdownAllServer");

        resp.setStatus(HttpServletResponse.SC_OK);
        resp.setContentType("application/json");
        
        PrintWriter writer = resp.getWriter();
        
        for (ServerDockerContainterProcess dstServerProcess : serverProcessesList) {
            if(dstServerProcess.getState() == 1)
            {
                System.out.println("start shutdown " + dstServerProcess.getArchiveID());

                dstServerDockerService.stopContainer(dstServerProcess.getContainterID());
            }
        }

        try {
            Thread.sleep(2000);
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }

        writer.println("success");
    }

    private void callBackServerByArchiveID(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String archiveID = req.getParameter("archiveID");

        System.out.println("request /callBackServerByArchiveID. archiveID=" + archiveID);
        boolean flag = true;
        Iterator<ServerDockerContainterProcess> iterator = serverProcessesList.iterator();
        while (iterator.hasNext()) {
            ServerDockerContainterProcess dstServerProcess = iterator.next();
            if (dstServerProcess.getArchiveID().equals(archiveID)) {
                flag = false;

                if (dstServerProcess.getState() == 1)
                {
                    String str = archiveID + "存档正在运行 无法停止.";
                    resp.setStatus(808);
                    System.err.println(str);
                    return;
                }

                break;
            }
        }

        if (flag) {
            String str = archiveID + "存档不存在";
            resp.setStatus(802);
            System.err.println(str);
            return;
        }

        Path archivePath = Paths.get(properties.getArchivePath()).toAbsolutePath();
        Path archiveFilePath = archivePath.resolve(archiveID);
        File archiveFile = new File(archiveFilePath.toString());

        String outputPath = properties.getUploadDir() + File.separator + archiveID + ".tar.gz";
        File tarGzFile = new File(outputPath);
        if( Base.compressDirectoryToTarGz(archiveFile, tarGzFile) != 0)
        {
            String str = archiveID + "存档压缩失败";
            System.err.println(str);
            resp.setStatus(803);
            return ;
        }

        if (tarGzFile.exists()) {
            // 设置响应内容类型
            resp.setContentType("application/octet-stream");
            resp.setHeader("Content-Disposition", "attachment; filename=\"" + archiveFile.getName() + "\"");
            resp.setContentLength((int) tarGzFile.length());

            FileInputStream inStream = null;
            OutputStream outStream = null;
            // 读取文件并写入响应
            try {
                inStream = new FileInputStream(tarGzFile);
                outStream = resp.getOutputStream();
                byte[] buffer = new byte[4096];
                int bytesRead;

                while ((bytesRead = inStream.read(buffer)) != -1) {
                    outStream.write(buffer, 0, bytesRead);
                }

                resp.setStatus(HttpServletResponse.SC_OK);  // 设置状态为 200

                return ;
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (inStream != null) {
                    inStream.close();
                }
                if (outStream != null) {
                    outStream.close();
                }
            }

        } else {
            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
        }
        resp.setStatus(801);
    }

    private void ackCallBackServer(HttpServletRequest req, HttpServletResponse resp) {
        String archiveID = req.getParameter("archiveID");
        System.out.println("request /ackCallBackServer: " + archiveID);

        resp.setStatus(200);

        ArrayList<ServerDockerContainterProcess> temps = new ArrayList<>();
        ServerDockerContainterProcess direct = new ServerDockerContainterProcess();
        Iterator<ServerDockerContainterProcess> iterator = serverProcessesList.iterator();
        while (iterator.hasNext()) {
            ServerDockerContainterProcess dstServerProcess = iterator.next();
            if (dstServerProcess.getArchiveID().equals(archiveID)) {
                temps.add(dstServerProcess);
                break;
            }
        }

        if (temps.isEmpty())
        {
            String str = "archive not existed: " + archiveID;
            System.out.println(str);
            return;
        }

        Path archivePath = Paths.get(properties.getArchivePath()).toAbsolutePath();
        Path archiveFilePath = archivePath.resolve(archiveID);
        File archiveFile = new File(archiveFilePath.toString());


        try {
            PrintWriter out = resp.getWriter();

            if (!archiveFile.exists())
            {
                String str = "archive had deleted: " + archiveID;
                System.out.println(str);
                out.println(str);
                // 根据archiveID从链表中删除存档
                iterator = serverProcessesList.iterator();
                while (iterator.hasNext()) {
                    ServerDockerContainterProcess dstServerProcess = iterator.next();
                    if (dstServerProcess.getArchiveID().equals(archiveID)) {
                        iterator.remove();
                        dstServerDockerService.delete(dstServerProcess.getContainterID());
                    }
                }
                return ;
            }

            if (Base.deleteDirectory(archiveFile))
            {
                String str = "archive deleted: " + archiveID;
                System.out.println(str);
                out.println(str);
                // 根据archiveID从链表中删除存档
                iterator = serverProcessesList.iterator();
                while (iterator.hasNext()) {
                    ServerDockerContainterProcess dstServerProcess = iterator.next();
                    if (dstServerProcess.getArchiveID().equals(archiveID)) {
                        iterator.remove();
                        dstServerDockerService.delete(dstServerProcess.getContainterID());
                    }
                }
                return ;
            } else {
                String str = "archive delete fail: " + archiveID;
                System.out.println(str);
                out.println(str);
                resp.setStatus(807);
                return ;
            }
        } catch (IOException e) {
            e.printStackTrace();
            String str = "archive delete fail: " + archiveID;
            System.out.println(str);
            resp.setStatus(807);
        }
    }

    /**
     * 获取所有modInfo配置
     *
     * @param req
     * @param resp
     * @throws IOException
     */
    private void getModsInfo(HttpServletRequest req, HttpServletResponse resp) {
        System.out.println("request /getModsInfo");

        DSTModService dstModService = new DSTModService(properties.getDstPath());

        Path dstPath = Paths.get(properties.getDstPath());
        Path modsPath = dstPath.resolve("mods");
        List<DSTMod> dstModList = dstModService.readAllModsInfo(modsPath);

        String resultStr = JSONUtil.toJsonStr(dstModList);
        // 设置响应类型
        resp.setContentType("application/json");
        resp.setStatus(HttpServletResponse.SC_OK);
        PrintWriter writer = null;
        try {
            writer = resp.getWriter();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        writer.println(resultStr);
    }

    private void console(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        System.out.println("request" + req.getPathInfo());

        resp.setStatus(HttpServletResponse.SC_OK);
        resp.setContentType("application/json");
        
        PrintWriter out = resp.getWriter();
        String archiveID = req.getParameter("archiveID");
        String shard = req.getParameter("shard");
        for (ServerDockerContainterProcess dstServerProcess : serverProcessesList) {
            if (dstServerProcess.getArchiveID().equals(archiveID) && dstServerProcess.getTag().equals(shard)) {
                if(req.getPathInfo().equals("/console/announce"))
                {
                    String message = req.getParameter(shard);
                    if(message == null)
                    {
                        out.println("need message");
                        resp.setStatus(801);
                        return ;
                    }
                    dstConsoleService.announce(message);
                }
                
                out.println("success");
                resp.setStatus(200);
                return ;
            }
        }
        out.println("no such server");
        resp.setStatus(801);
    }

    private void updateDst(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        System.out.println("request: " + req.getPathInfo());

        resp.setStatus(HttpServletResponse.SC_OK);
        resp.setContentType("text/plain");
        
        PrintWriter out = resp.getWriter();
        for (ServerDockerContainterProcess dstServerProcess : serverProcessesList) {
            if (dstServerProcess.getState() == 1)
            {
                out.println("error: " + dstServerProcess.getArchiveID() + "is running.");
                resp.setStatus(801);
            }
        }
        
        String steamshPath = properties.getSteamshPath();
        String dstPath = properties.getDstPath();
        String cmd = "cd " + steamshPath + " ; ./steamcmd.sh +login anonymous +force_install_dir "+ dstPath +" +app_update 343050 validate +quit";
        ShellUtil shellUtil = new ShellUtil();
        if (shellUtil.runShell(cmd).size() == 0)
        {
            out.println("error: update dst fail.");
            resp.setStatus(801);
        }
    }

    private void test(HttpServletRequest req, HttpServletResponse resp) 
    {   
        System.out.println("[debug] haveString: " + Base.haveString("/home/ubuntu/.klei/DoNotStarveTogether/testServer@4/Master/server_log.txt", "MasterPort:"));
        resp.setStatus(200);
    }

}